<?php
/**
 * vim: tabstop=4
 * 
 * @license		http://www.gnu.org/licenses/gpl.html GPL Version 3
 * @author		Ian Moore <imooreyahoo@gmail.com>
 * @copyright	Copyright (c) 2011 Ian Moore
 *
 * This file is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this file. If not, see <http://www.gnu.org/licenses/>.
 */
require_once("openmediavault/object.inc");
require_once("openmediavault/error.inc");
require_once("openmediavault/util.inc");
require_once("openmediavault/rpc.inc");

class CupsRpc extends OMVRpc {

	const xpathRoot = '//services/cups';

	public function __construct() {
		$this->methodSchemata = array(
            "set" => array('{
                "type":"object",
                "properties":{
                    "enable":{"type":"boolean"},
                    "enable_samba":{"type":"boolean"},					
                    "airprint":{"type":"boolean"},
					"maxjobs":{"type":"integer"}
                }
            }'),
			
			"cupsAddPrinter" => array('{
				"type":"object",
				"properties": {
					"device":{"type":"string"},
					"ppd":{"type":"string"},
					"name":{"type":"string"},
					"location":{"type":"string","optional":true},
					"description":{"type":"string"}
				}
			}'),
			"cupsDeletePrinter" => array('{
				"type":"string"
			}'),
			"cupsPrintTestPage" => array('{
				"type":"string"
			}'),
			"cupsResumePrinting" => array('{
				"type":"string"
			}'),
			"cupsPausePrinting" => array('{
				"type":"string"
			}'),

			"cupsCancelJobs" => array('{
				"type":"string"
			}'),
			"cupsCancelJob" => array('{
				"type":"integer"
			}'),
			"cupsSetPrinter" => array('{
				"type":"object",
				"properties": {
					"uuid":{"type":"string"},
					"PrinterLocation":{"type":"string","optional":true},
					"PrinterInfo":{"type":"string"}
				}
			}'),
            "cupsGetJobs" => array(
                '{"type":"integer"}', // start
                '{"type":"integer"}', // count
                '{'.$GLOBALS['OMV_JSONSCHEMA_SORTFIELD'].'}', // sortField
                '{'.$GLOBALS['OMV_JSONSCHEMA_SORTDIR'].'}', // sortDir
				'{"type":"string", "optional": true}' // jobType
            ),

			"cupsGetPrinter" => array('{
				"type":"string"
			}')			
		);

	}

    /**
     * Safe config getting amd setting
     */
    public function __call($name, $args) {

        // Configuration methods
        if(substr($name,0,6) == 'config') {

            // Correct method name
            $name = substr($name,6);
            $name[0] = strtolower($name[0]);

            global $xmlConfig;
            $object = call_user_func_array(array($xmlConfig,$name),$args);
            switch($name) {
                case 'delete':
                    if($object === false)
                        throw new OMVException(OMVErrorMsg::E_CONFIG_OBJECT_NOT_FOUND, $args[0]);
                    break;
                case 'save':
                    if($object === false)
                        throw new OMVException(OMVErrorMsg::E_CONFIG_SAVE_FAILED, $xmlConfig->getError());
                    break;
                case 'set':
                case 'replace':
                    if($object === false)
                        throw new OMVException(OMVErrorMsg::E_CONFIG_SET_OBJECT_FAILED);
                    break;
                default:
                    if(is_null($object))
                        throw new OMVException(OMVErrorMsg::E_CONFIG_GET_OBJECT_FAILED, $args[0]);
            }

            return $object;

		}
        throw new Exception("Method ".__CLASS__."::".$name." does not exist.");
    }

    /**
     * Verify that the current user is an admin, and validate method args
     */
    private function _validate($mname='',$args=array()) {

        // Check permissions
        $this->validateSession();
        if (!$this->hasRole(OMV_ROLE_ADMINISTRATOR)) {
            throw new OMVException(OMVErrorMsg::E_RPC_SERVICE_INVALID_PERMISSION);
        }
        $this->commitSession();

        // Check incoming data
        if($mname)
            $this->validateParams($mname, $args);
    }


	/**
	 * Run a cups command
	 */
	private function _cups($fn, $args=array()) {

		if(count($args)) {
			$args = implode(" ", array_map('escapeshellarg', $args));
		} else {
			$args = '';
		}
		
		$cmd = "sudo /usr/local/bin/omvcups.py {$fn} {$args} 2>&1";
		OMVUtil::exec($cmd, $output, $result);
		if ($result !== 0) {
			throw new OMVException(OMVErrorMsg::E_EXEC_FAILED,
			  $cmd, implode("\n", $output));
		}
		
		$obj = json_decode(implode("\n",$output), true);
		
		if(!empty($obj['error'])) {
			throw new Exception($obj['error']);
		}
		
		return $obj;
		
	}
	
	
	/**
	 * Restart samba
	 */
	function restartSamba() {
		
		$this->_validate();
		
		# Regenerate service definitions
		$cmd = "sudo /etc/init.d/samba restart 2>&1";
		OMVUtil::exec($cmd, $output, $result);
		if ($result !== 0) {
			throw new OMVException(OMVErrorMsg::E_EXEC_FAILED,
			$cmd, implode("\n", $output));
		}
		
	}
	
	/**
	 * Resync avahi service definitions
	 */
	public function resync() {
		
		$this->_validate();
				
		# Regenerate service definitions
		$cmd = "sudo /usr/local/bin/omvcups-avahi.py 2>&1";
		OMVUtil::exec($cmd, $output, $result);
		if ($result !== 0) {
			throw new OMVException(OMVErrorMsg::E_EXEC_FAILED,
			$cmd, implode("\n", $output));
		}
		
		
	}
	
	
	/**
	 * Get printer jobs
	 */
	function cupsGetJobs($start, $count, $sortField, $sortDir, $which='not-completed') {
		
		$this->_validate(__METHOD__,func_get_args());
		
		// Filter result
        return $this->applyFilter($this->_cups('getJobs', array($which)), $start, $count, $sortField, $sortDir);
	}
	
	/**
	 * Delete a printer
	 */
	function cupsDeletePrinter($name) {
		
		$this->_validate(__METHOD__,func_get_args());
		
		$this->_cups('deletePrinter', array($name));
		
		// Resync services
		$this->resync();
		
	}
	
	/**
	 * Send a test page to printer
	 */
	function cupsPrintTestPage($name) {
		
		$this->_validate(__METHOD__,func_get_args());
		
		$this->_cups('printTestPage', array($name));
	}

	/**
	 * cupsPausePrinting
	 */
	function cupsPausePrinting($name) {
		
		$this->_validate(__METHOD__,func_get_args());
		
		$this->_cups('pausePrinting', array($name));
	}

	/**
	 * Send a test page to printer
	 */
	function cupsResumePrinting($name) {
		
		$this->_validate(__METHOD__,func_get_args());
		
		$this->_cups('resumePrinting', array($name));
	}

	/**
	 * Cancel a single job
	 */
	function cupsCancelJob($jobid) {
		
		$this->_validate(__METHOD__,func_get_args());
		
		$this->_cups('cancelJob', array($jobid));
	}


	/**
	 * Cancel all jobs for pritner
	 */
	function cupsCancelJobs($name) {
		
		$this->_validate(__METHOD__,func_get_args());
		
		$this->_cups('cancelAllJobs', array($name));
	}

	/**
	 * Get a list of makes and models
	 */
	function cupsGetMakesModels() {
		
		$this->_validate();
		
		return $this->_cups('getMakesModels');
	}
	
	/**
	 * Get a list of directly connected devices
	 */
	function cupsFindDirectPrinters() {
		
		$this->_validate();
		
		return $this->_cups('findDirectPrinters');
		
	}
	
	
	/**
	 * Get a specific printer
	 */
	function cupsGetPrinter($name) {
		
		$this->_validate(__METHOD__, func_get_args());
		
		return $this->_cups('getPrinter', array($name));
		
	}

	/**
	 * Set printer properties
	 */
	function cupsSetPrinter($data) {
		
		$this->_validate(__METHOD__, func_get_args());
		
		// fname, fppdname, finfo, flocation, fdevice
		if(empty($data['PrinterLocation'])) $data['PrinterLocation'] = '';
		$this->_cups('setPrinter', array($data['uuid'], $data['PrinterInfo'], $data['PrinterLocation']));
				
		// Resync services
		$this->resync();
	}
	
	/**
	 * Add a printer
	 */
	function cupsAddPrinter($data) {
		
		$this->_validate(__METHOD__, func_get_args());
		
		// Search for existing printer with that name
		$p = null;
		try {
			$p = $this->cupsGetPrinter($data['name']);
		} catch (Exception $e) {
			// nothing
		}
		if($p) {
			throw new Exception("A printer with the name {$data['name']} already exists.");
		}
		
		// fname, fppdname, finfo, flocation, fdevice
		if(empty($data['location'])) $data['location'] = '';
		$this->_cups('addPrinter', array($data['name'], $data['ppd'], $data['description'], $data['location'], $data['device']));
		
		// Resync services
		$this->resync();
				
	}
	
	/**
	 * Get settings and printers
	 */
	function get() {

		$this->_validate();

		$config = $this->configGet(self::xpathRoot);
		
		$config['enable'] = boolval($config['enable']);
		$config['enable_samba'] = boolval($config['enable_samba']);
		
		if($config['enable']) $config['printers'] = $this->_cups('getPrinters');
		else $config['printers'] = array();
		
		// see if samba is enabled
		$config['smbenabled'] = boolval(intval($this->configGet("//services/smb/enable")) == 1);
		
		
		return $config;

	}

	/**
	 * Get a list of printers
	 */
	function cupsGetPrinters() {
		
		$this->_validate();
		return $this->_cups('getPrinters');
		
	}
	
	/**
	* Set settings
	*/
	function set($object) {

       // Check incoming data
        $this->_validate(__METHOD__,func_get_args());

		$oldconf = $this->get();
        $object = array(
        	'enable' => array_boolval($object,'enable'),
			'enable_samba' => array_boolval($object,'enable_samba'),
        	'airprint' => array_boolval($object,'airprint'),
			'maxjobs' => intval($object['maxjobs'])
        );

        // Set configuration object
        $this->configReplace(self::xpathRoot, $object);

        $this->configSave();

        // Notify general configuration changes
        $dispatcher = &OMVNotifyDispatcher::getInstance();
        $dispatcher->notify(OMV_NOTIFY_MODIFY,
            "org.openmediavault.services.cups", $object, $oldconf);

		// Notify samba of changes?
		if($oldconf['enable'] != $object['enable']) {

			$smbobject = $this->configGet("//services/smb");

	        $dispatcher->notify(OMV_NOTIFY_MODIFY,
    	      "org.openmediavault.services.smb", $smbobject);
	        
	        // If we're enabled, make sure sharing is enabled
	        if($object['enable']) {
	        	$this->_cups('enableSharing');
	        }
	        
		}

	}
}

